1 Introduction

The requirement to “re-balance” bicycles throughout the network is one of the difficult operational issues faced by urban bike share systems. If there are no bikes available for pickup at a dock or available docking spots to leave a bike, bike share is useless. Re-balancing is the activity of physically redistributing bikes to ensure that a bike or a docking location is accessible when needed while anticipating (or projecting) bike sharing demand for all docks at all times.

Rebalancing can be achieved by multiple ways, transporting excess bikes to where they are needed by trucks might be one of the most operational and efficient ways. Of course, it is also a feasible way to encourage cyclists to park their bicycles in designated locations through appropriate incentives. All in all, almost all rebalancing methods are highly dependent on accurate analysis and prediction of the spatial and time distribution of bikes. In this project, the LA Metro Bike Share trip data (source:https://bikeshare.metro.net/about/data/) for 2022 Q2 will be used to build models and forecast space/time demand for bike share usages.

2 Set up

First of all, start with the required libraries and functions.

library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(ggplot2)
library(gifski)
library(gganimate)
library(caret)
plotTheme <- theme(
  plot.title =element_text(size=12),
  plot.subtitle = element_text(size=8),
  plot.caption = element_text(size = 6),
  axis.text.x = element_text(size = 10, angle = 45, hjust = 1),
  axis.text.y = element_text(size = 10),
  axis.title.y = element_text(size = 10),
  # Set the entire chart region to blank
  panel.background=element_blank(),
  plot.background=element_blank(),
  #panel.border=element_rect(colour="#F0F0F0"),
  # Format the grid
  panel.grid.major=element_line(colour="#D0D0D0",size=.2),
  axis.ticks=element_blank())
mapTheme <- theme(plot.title =element_text(size=12),
                  plot.subtitle = element_text(size=8),
                  plot.caption = element_text(size = 6),
                  axis.line=element_blank(),
                  axis.text.x=element_blank(),
                  axis.text.y=element_blank(),
                  axis.ticks=element_blank(),
                  axis.title.x=element_blank(),
                  axis.title.y=element_blank(),
                  panel.background=element_blank(),
                  panel.border=element_blank(),
                  panel.grid.major=element_line(colour = 'transparent'),
                  panel.grid.minor=element_blank(),
                  legend.direction = "vertical", 
                  legend.position = "right",
                  plot.margin = margin(1, 1, 1, 1, 'cm'),
                  legend.key.height = unit(1, "cm"), legend.key.width = unit(0.2, "cm"))
palette5 <- c("#FB8C6F","#73607D","#C1B9AE","#FDC664","#355C7D")
palette4 <- c("#FB8C6F","#73607D","#C1B9AE","#FDC664")
palette2 <- c("#FB8C6F","#73607D")

2.1 Import bike share data

The LA Metro Bike Share trip data (source:https://bikeshare.metro.net/about/data/) for 2022 Q2 is read in.

dat <- st_read("./metro-trips-2022-q2.csv") 
station_name <- st_read("./metro-bike-share-stations-2022-10-01.csv")

colnames(station_name)[colnames(station_name) == 'Station_ID'] <- "start_station"

dat <-
  merge(dat, station_name[c("Station_Name", "start_station")], by = "start_station")
#  st_transform('ESRI:102286')

As below, parse_date_time function is used to standardize the start_time field (when a trip departed) into the 1 hour and 15 minute intervals needed for later analysis. Function week and wday convert the data/time stamp into week of the year and day of the week, respectively.

dat2 <- dat %>%
  mutate(interval60 = floor_date(mdy_hm(start_time), unit = "hour"),
         interval15 = floor_date(mdy_hm(start_time), unit = "15 mins"),
         week = week(interval60),
         dotw = wday(interval60, label=TRUE)) %>%
  mutate(new_start_time = strptime(as.character(start_time), "%m/%d/%Y %H:%M"),
         new_end_time = strptime(as.character(end_time), "%m/%d/%Y %H:%M")) %>%
  filter(week>17 & week<23)
glimpse(dat2)
## Rows: 26,989
## Columns: 22
## $ start_station       <chr> "3000", "3000", "3005", "3005", "3005", "3005", "3…
## $ trip_id             <chr> "193851816", "193855710", "191014162", "194147504"…
## $ duration            <chr> "4", "74", "4", "64", "1", "16", "16", "4", "84", …
## $ start_time          <chr> "5/27/2022 14:59", "5/27/2022 15:04", "5/1/2022 18…
## $ end_time            <chr> "5/27/2022 15:03", "5/27/2022 16:18", "5/1/2022 18…
## $ start_lat           <chr> "", "", "34.0485", "34.0485", "34.0485", "34.0485"…
## $ start_lon           <chr> "", "", "-118.258537", "-118.258537", "-118.258537…
## $ end_station         <chr> "3000", "3000", "3031", "3005", "3005", "3030", "3…
## $ end_lat             <chr> "", "", "34.044701", "34.0485", "34.0485", "34.051…
## $ end_lon             <chr> "", "", "-118.252441", "-118.258537", "-118.258537…
## $ bike_id             <chr> "6335", "6335", "20052", "14901", "12158", "5984",…
## $ plan_duration       <chr> "30", "365", "30", "1", "30", "30", "30", "30", "3…
## $ trip_route_category <chr> "Round Trip", "Round Trip", "One Way", "Round Trip…
## $ passholder_type     <chr> "Monthly Pass", "Annual Pass", "Monthly Pass", "On…
## $ bike_type           <chr> "standard", "standard", "standard", "standard", "s…
## $ Station_Name        <chr> "Virtual Station", "Virtual Station", "7th & Flowe…
## $ interval60          <dttm> 2022-05-27 14:00:00, 2022-05-27 15:00:00, 2022-05…
## $ interval15          <dttm> 2022-05-27 14:45:00, 2022-05-27 15:00:00, 2022-05…
## $ week                <dbl> 21, 21, 18, 22, 20, 20, 20, 22, 20, 22, 20, 19, 21…
## $ dotw                <ord> Fri, Fri, Sun, Mon, Sat, Mon, Thu, Wed, Tue, Tue, …
## $ new_start_time      <dttm> 2022-05-27 14:59:00, 2022-05-27 15:04:00, 2022-05…
## $ new_end_time        <dttm> 2022-05-27 15:03:00, 2022-05-27 16:18:00, 2022-05…

2.2 Import Census Info

The code block below pulls census geography and some variables from the tidycensus package. These will be used to demonstrate generalizability later, but won’t be used as independent variables because they end up being perfectly colinear with the stations fixed effects. Spatial information will be added to ridershare data as origin and destination data.

LACensus <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2020, 
          state = "CA", 
          geometry = TRUE, 
          county=c("Los Angeles"),
          output = "wide") %>%
  rename(Total_Pop =  B01003_001E,
         Med_Inc = B19013_001E,
         Med_Age = B01002_001E,
         White_Pop = B02001_002E,
         Travel_Time = B08013_001E,
         Num_Commuters = B08012_001E,
         Means_of_Transport = B08301_001E,
         Total_Public_Trans = B08301_010E) %>%
  select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
         Means_of_Transport, Total_Public_Trans,
         Med_Age,
         GEOID, geometry) %>%
  mutate(Percent_White = White_Pop / Total_Pop,
         Mean_Commute_Time = Travel_Time / Total_Public_Trans,
         Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
LATracts <- 
  LACensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf
dat2$start_lon[dat2$start_lon==''] <- NA
dat2$start_lat[dat2$start_lat==''] <- NA
dat2$end_lon[dat2$end_lon==''] <- NA
dat2$end_lat[dat2$end_lat==''] <- NA

dat_census <-  st_join(dat2 %>% 
          filter(is.na(start_lon) == FALSE &
                   is.na(start_lat) == FALSE &
                   is.na(end_lat) == FALSE &
                   is.na(end_lon) == FALSE )%>%
          st_as_sf(., coords = c("start_lon", "start_lat"), crs = 4326),
        LATracts %>%
          st_transform(crs=4326),
        join=st_intersects,
              left = TRUE) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(start_lon = unlist(map(geometry, 1)),
         start_lan = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)%>%
  st_as_sf(., coords = c("end_lon", "end_lat"), crs = 4326) %>%
  st_join(., LATracts %>%
            st_transform(crs=4326),
          join=st_intersects,
          left = TRUE) %>%
  rename(Destination.Tract = GEOID)  %>%
  mutate(end_lon = unlist(map(geometry, 1)),
         end_lat = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)

2.3 Import Weather Data

It would be reasonable to anticipate that bad weather in the Windy City would encourage ride sharing. Here, I imported weather data from Los Angeles Downtown/USC (SID code: CQT, serving period from 1999 till now) using riem_measures between 2022-05-01 and 2022-05-31.

Below a weather.Panel is generated to summarize temperature, precipitation and wind speed for every hour in May 2022.

weather.Panel <- 
  riem_measures(station = "CQT", date_start = "2022-04-30", date_end = "2022-06-03") %>%
  dplyr::select(valid, tmpf, p01i, sknt)%>%
  replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    mutate(week = week(interval60),
           dotw = wday(interval60, label=TRUE)) %>%
    group_by(interval60) %>%
    summarize(Temperature = max(tmpf),
              Precipitation = sum(p01i),
              Wind_Speed = max(sknt)) %>%
    mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))

glimpse(weather.Panel)
## Rows: 816
## Columns: 4
## $ interval60    <dttm> 2022-04-30 00:00:00, 2022-04-30 01:00:00, 2022-04-30 02…
## $ Temperature   <dbl> 64.9, 62.1, 60.1, 59.0, 57.9, 57.9, 57.0, 57.0, 55.9, 55…
## $ Precipitation <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Wind_Speed    <dbl> 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,…

Below the weather data is plotted as a time series using grid.arrange.

grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() + 
  labs(title="Percipitation", x="Hour", y="Perecipitation") + plotTheme,
  ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line() + 
    labs(title="Wind Speed", x="Hour", y="Wind Speed") + plotTheme,
  ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() + 
    labs(title="Temperature", x="Hour", y="Temperature") + plotTheme,
  top="Weather Data - LA CQT - May, 2022")

3 Describe and Explore the Data

3.1 Create Space-Time Panel

To make sure that each unique and hour/day combo exists in our data set, an empty study.panel is created which has each unique space/time observations. No matter an observation took place or not, each time period in the study is represented by a row in it.

The maximum number of combinations can be calculated by 24 hours per day * 7 days per week * 5 weeks * the number of stations. Then, compare that to the number of rows in study.panel by nrow. Both steps show the same result: 174528, which means that the counts is correct.

Continue to join the station name, tract and latitude/longitude.

length(unique(dat_census$interval60)) * length(unique(dat_census$start_station))
## [1] 174528
study.panel <- 
  expand.grid(interval60=unique(dat_census$interval60), 
              start_station = unique(dat_census$start_station)) %>%
  left_join(., dat_census %>%
              select(start_station, Station_Name, Origin.Tract, start_lon, start_lan )%>% 
              distinct() %>%
              group_by(start_station) %>%
              slice(1))
nrow(study.panel)      
## [1] 174528

In the code below, a full panel is summarizing by station for each time interval, keep cansus info and latitude/longitude information along for joining later to other data. Start station IDs are removed by FALSE.

ride.panel <- 
  dat_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, start_station, Station_Name, Origin.Tract, start_lon, start_lan) %>% # missing "from_station_name"
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(start_station) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE)
ride.panel <- 
  left_join(ride.panel, LACensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

3.2 Create time lags

ride.panel <- 
  ride.panel %>% 
  arrange(start_station, interval60) %>% 
  mutate(lagHour = dplyr::lag(Trip_Count,1),
         lag2Hours = dplyr::lag(Trip_Count,2),
         lag3Hours = dplyr::lag(Trip_Count,3),
         lag4Hours = dplyr::lag(Trip_Count,4),
         lag12Hours = dplyr::lag(Trip_Count,12),
         lag1day = dplyr::lag(Trip_Count,24),
         holiday = ifelse(yday(interval60) == 148,1,0)) %>%
   mutate(day = yday(interval60)) %>%
   mutate(holidayLag = case_when(dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay",
                                 dplyr::lag(holiday, 2) == 1 ~ "PlustTwoDays",
                                 dplyr::lag(holiday, 3) == 1 ~ "PlustThreeDays",
                                 dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
                                 dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
                                 dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
         holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag))
as.data.frame(ride.panel) %>%
    group_by(interval60) %>% 
    summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
    gather(Variable, Value, -interval60, -Trip_Count) %>%
    mutate(Variable = factor(Variable, levels=c("lagHour","lag2Hours","lag3Hours","lag4Hours",
                                                "lag12Hours","lag1day")))%>%
    group_by(Variable) %>%  
    summarize(correlation = round(cor(Value, Trip_Count),2))
## # A tibble: 6 × 2
##   Variable   correlation
##   <fct>            <dbl>
## 1 lagHour           0.89
## 2 lag2Hours         0.76
## 3 lag3Hours         0.59
## 4 lag4Hours         0.39
## 5 lag12Hours       -0.81
## 6 lag1day           0.83

3.3 Traning/testing data set

Here, a time series approach is taken, a training data set with 3 weeks of data (weeks 18-20) and a testing data set with 2 weeks of data (weeks 21-22) are created. Code below split the data by week.

ride.Train <- filter(ride.panel, week >= 18 & week <= 20)
ride.Test <- filter(ride.panel, week >=21 & week <= 22)

3.4 Exploratoring data

In this section, the rider share data is explored for time, space, weather, and demographic relationships. If the Trip_Count exhibit serial(temporal) correlation, the time lag features will lead to better predictions.

3.4.1 Serial autocorrelation

We begin by examining the time and frequency components of our data.

First, we look at the overall time pattern - there is clearly a daily periodicity and there are lull periods on weekends. geo-vline is used to visualize Mondays. All of the five weeks exhibit similar time series patterns with consistent peaks and troughs. Which suggests the presence of serial correlation.

mondays <- 
  mutate(ride.panel,
         monday = ifelse(dotw == "Mon" & hour(interval60) == 1,
                         interval60, 0)) %>%
  filter(monday != 0) 

st_drop_geometry(rbind(
  mutate(ride.Train, Legend = "Training"), 
  mutate(ride.Test, Legend = "Testing"))) %>%
    group_by(Legend, interval60) %>% 
      summarize(Trip_Count = sum(Trip_Count)) %>%
      ungroup() %>% 
      ggplot(aes(interval60, Trip_Count, colour = Legend)) + geom_line() +
        scale_colour_manual(values = palette2) +
        geom_vline(data = mondays, aes(xintercept = monday)) +
        labs(title="LA Rideshare trips by week: Apr 30 - Jun 03, 2022",
             subtitle="Dotted lines for Mondays", 
             x="Day", y="Trip Count",
             caption = "Figure 3-1") +
        plotTheme + theme(panel.grid.major = element_blank())    

Next, the time lag features are tested for correlation with Trip_Count. plotData.lag returns the Trip_Count and time lag features for week 18. fct_relevel reorders the lag levels. Omit that line and try levels(plotData.lag$Variable).

Pearson correlation is then calculated for each Variable in correlation.lag.

Weak Trip_Count correlations are visualized below.

plotData.lag <-
  filter(as.data.frame(ride.panel), week == 18) %>%
  dplyr::select(starts_with("lag"), Trip_Count) %>%
  gather(Variable, Value, -Trip_Count) %>%
  mutate(Variable = fct_relevel(Variable, "lagHour","lag2Hours","lag3Hours",
                                          "lag4Hours","lag12Hours","lag1day"))
correlation.lag <-
  group_by(plotData.lag, Variable) %>%
    summarize(correlation = round(cor(Value, Trip_Count, use = "complete.obs"), 2)) 

plotData.lag %>%
ggplot()+
  geom_point(aes(x= Value, y = Trip_Count))+
  geom_text(data = correlation.lag, aes(label = paste("r =", round(correlation, 2))),
            x=-Inf, y=Inf, vjust = 1.5, hjust = -.1) +
  geom_smooth(aes(x= Value, y= Trip_Count), method = "lm", se = FALSE, color = "73607D")+
  geom_abline(slope = 1, intercept = 0)+
  facet_wrap(~Variable, ncol = 2, scales = "free") +
  labs(title="Ridershare trip count as a function of time lags",
       x="Lag Trip Count", 
       y="Trip_Count",
       caption = "Figure 3-2")+
  plotTheme

Figure 3-3 illustrates the distribution of trip volume by station for different times of the day. There are some stations with high volume periods, but more have low volume. Therefore, our data will consist of a number of low demand station/hours and a few high demand station hours.

We can also track the daily trends in ridership by day of the week and weekend versus weekday, to see what temporal patterns we’d like to control for.

dat_census %>%
        mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
         group_by(interval60, start_station, time_of_day) %>%
         tally()%>%
  group_by(start_station, time_of_day)%>%
  summarize(mean_trips = mean(n))%>%
  ggplot()+
  geom_histogram(aes(mean_trips), binwidth = 1)+
  labs(title="Mean Number of Hourly Trips Per Station. LA, Apr 30 - Jun 03, 2022",
       x="Number of trips", 
       y="Frequency",
       caption = "Figure 3-3")+
  facet_wrap(~time_of_day)+
  plotTheme

ggplot(dat_census %>%
         group_by(interval60, start_station) %>%
         tally())+
  geom_histogram(aes(n), binwidth = 5)+
  labs(title="Bike share trips per hr by station. LA, Apr 30 - Jun 03, 2022",
       x="Trip Counts", 
       y="Number of Stations",
       caption = "Figure 3-4")+
  plotTheme

Figure 3-5 and 3-6 illustrate total Trip_Counts distribution in one day by weekdays and weekends. It clear that the trip volume of each day in a week have similar patterns: reaching peak at about 400 in 12:00-17:00 and falling at near 0 value in 3:00 or 24:00. Weekdays have almost double peak volume than weekends.

ggplot(dat_census %>% mutate(hour = hour(new_start_time)))+
     geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
  labs(title="Bike share trips in LA, by day of the week, May, 2022",
       x="Hour", 
       y="Trip Counts",
       caption = "Figure 3-5")+
     plotTheme

ggplot(dat_census %>% 
         mutate(hour = hour(new_start_time),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
     geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
  labs(title="Bike share trips in LA - weekend vs weekday, Apr 30 - Jun 03, 2022",
       x="Hour", 
       y="Trip Counts",
       caption = "Figure 3-6")+
     plotTheme

3.4.2 Spatial autocorrelation

In this part, the spatial autocorrelation will be examined. Figure 3-7 shows the Trip-Count per hour by station in weekdays and weekends, respectively.

ggplot()+
  geom_sf(data = LATracts %>%
          st_transform(crs=4326))+
  geom_point(data = dat_census %>% 
            mutate(hour = hour(new_start_time),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
                time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
              group_by(start_station, start_lan, start_lon, weekend, time_of_day) %>%
              tally(),
            aes(x=start_lon, y = start_lan, color = n), 
            fill = "transparent", alpha = 0.5, size = 0.5)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$start_lan), max(dat_census$start_lan))+
  xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
  facet_grid(weekend ~ time_of_day)+
  labs(title="Bike share trips per hr by station. LA, May, 2022",
       caption = "Figure 3-7")+
  mapTheme

3.4.3 Space/time correlation

week20 <-
  filter(ride.panel , week == 20 & dotw == "Mon")

week20.panel <-
  expand.grid(
    interval60 = unique(week20$interval60),
    start_station = unique(ride.panel$start_station))
ride.animation.data <-
  mutate(week20, Trip_Counter = 1) %>%
  select(interval60, start_station, start_lon, start_lan, Trip_Counter) %>%
    right_join(week20.panel) %>% 
    group_by(interval60, start_station, start_lon, start_lan,) %>%
    summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>% 
    ungroup() %>%
    mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
                             Trip_Count > 0 & Trip_Count <= 2 ~ "1-2 trips",
                             Trip_Count > 2 & Trip_Count <= 5 ~ "3-5 trips",
                             Trip_Count > 5 & Trip_Count <= 10 ~ "6-10 trips",
                             Trip_Count > 10 & Trip_Count <= 15 ~ "11-15 trips",
                             Trip_Count > 15 ~ "16+ trips")) %>%
    mutate(Trips  = fct_relevel(Trips, "0 trips","1-2 trips","3-5 trips",
                                       "6-10 trips","6-10 trips","16+ trips"))
rideshare_animation <-
  ggplot() +
    geom_sf(data = LATracts %>%
              st_transform(crs=4326)) +
    geom_point(data = ride.animation.data, 
               aes(x = start_lon, y = start_lan, fill = Trips), size = 0.5, alpha = 1.5)+
    scale_color_manual(values = palette5) +
    labs(title = "Rideshare pickups for one day in May 2022, LA",
         subtitle = "60 minute intervals: {current_frame}",
         caption = "Figure 3-8") +
    transition_manual(interval60) +
    mapTheme

animate(rideshare_animation, duration=75, renderer = gifski_renderer())
rideshare_animation <-
  ggplot() +
    geom_sf(data = ride.animation.data, aes(fill = Trips)) +
    scale_fill_manual(values = palette5) +
    labs(title = "Rideshare pickups for one day in November 2018",
         subtitle = "15 minute intervals: {current_frame}") +
    transition_manual(interval15) +
    mapTheme

4 Modelling

4.1 Building model

In this section, five different linear regressions are estimated on ride.Train, each with different fixed effects: 1. reg1 focuses on just time, including hour fixed effects, day of the week, and Temperature. 2. reg2 focuses on just space effects with the start_station fixed effects. 3. reg3 includes both time and space fixed effects. 4. reg4 adds the time lag features. 5. reg5 adds the holiday lag features.

reg1 <- 
  lm(Trip_Count ~  hour(interval60) + dotw + Temperature,  data=ride.Train)
reg2 <- 
  lm(Trip_Count ~  start_station + dotw + Temperature,  data=ride.Train)
reg3 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation, 
     data=ride.Train)
reg4 <- 
  lm(Trip_Count ~  start_station +  hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day, 
     data=ride.Train)
reg5 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holiday, # + holidayLag ,
     data=ride.Train)

4.2 Predict for test data

Models built above will be used for predicting data in ride.Test which includes 2 sf tibbles (with geometries).

ride.Test.weekNest <- 
  ride.Test %>%
  nest(-week) 

ride.Test.weekNest
## # A tibble: 2 × 2
##    week data                  
##   <dbl> <list>                
## 1    21 <tibble [35,096 × 30]>
## 2    22 <tibble [34,026 × 30]>

Next, a function is created that takes a tibble, dat and a regression model, fit as its inputs, and outputs predictions as pred. Each week in ride.Trest.weekNest can be predicted by this function.

model_pred <- function(dat, fit){
   pred <- predict(fit, newdata = dat)}

The code below will loop through each model for each testing week and mutate summary statistics, including the predictions, observed, absolute error, MAE and SD AE.

week_predictions <- 
  ride.Test.weekNest %>% 
    mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
           BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
           CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
           DTime_Space_FE_timeLags = map(.x = data, fit = reg4, .f = model_pred),
           ETime_Space_FE_timeLags_holidayLags = map(.x = data, fit = reg5, .f = model_pred)) %>% 
  
    gather(Regression, Prediction, -data, -week) %>%
    mutate(Observed = map(data, pull, Trip_Count),
           Absolute_Error = map2(Observed, Prediction,  ~ abs(.x - .y)),
           MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
           sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))
week_predictions

A 2 (weeks) times 5 (models) table is created above, each row presents the statistics of one week in ride.Trest.weekNest predicted by a regression model.

5 Examing accuracy

In this section, Mean Absolute Error(MAE) is calculated on ride.Test for each model.

5.1 Examine Error Metrics for Accuracy

Figure 5-1 shows goodness of fit by week and model. The MAE for the time effects model (reg1) in week 21 is comparable to the mean observed Trip_Count of 0.256. With increasing sophistication, the model becomes more accurate and more generalizable.

week_predictions %>%
  dplyr::select(week, Regression, MAE) %>%
  gather(Variable, MAE, -Regression, -week) %>%
  ggplot(aes(week, MAE)) + 
    geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
    scale_fill_manual(values = palette5) +
    labs(title = "Mean Absolute Errors by model specification and week",
         caption = "Figure 5-1") +
  plotTheme

For each model, predicted and observed Trip_Count is taken out of the spatial context and their means of plotted in time series form below. It seems that model A and C have the same time trend because the spatial context has been removed.

The time lags facilitate the capacity to predict for the highest peaks as sophistication increases.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station)) %>%
    dplyr::select(interval60, start_station, Observed, Prediction, Regression) %>%
    unnest() %>%
    gather(Variable, Value, -Regression, -interval60, -start_station) %>%
    group_by(Regression, Variable, interval60) %>%
    summarize(Value = sum(Value)) %>%
    ggplot(aes(interval60, Value, colour=Variable)) + 
      geom_line(size = 1.1) + 
      facet_wrap(~Regression, ncol=1) +
      labs(title = "Predicted/Observed bike share time series", subtitle = "LA; A test set of 2 weeks",  x = "Hour", y= "Station Trips",
         caption = "Figure 5-2") +
      plotTheme

Figure 5-3 illustrates the observed and predicted distribution for different times of day durig weekdays and weekends. It is clear that the models are under predicting in general.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lan = map(data, pull, start_lan), 
           start_lon = map(data, pull, start_lon),
           dotw = map(data, pull, dotw)) %>%
    select(interval60, start_lan, start_lon, 
           start_station, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
  ggplot()+
  geom_point(aes(x= Observed, y = Prediction))+
    geom_smooth(aes(x= Observed, y= Prediction), method = "lm", se = FALSE, color = "red")+
    geom_abline(slope = 1, intercept = 0)+
  facet_grid(time_of_day~weekend)+
  labs(title="Observed vs Predicted",
       x="Observed trips", 
       y="Predicted trips",
         caption = "Figure 5-3")+
  plotTheme

5.2 Space-Time Error Evaluation

Figure 5-4 plots mean absolute errors on map by weekdays/weekends and time of a day.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lan = map(data, pull, start_lan), 
           start_lon = map(data, pull, start_lon),
           dotw = map(data, pull, dotw) ) %>%
    select(interval60, start_station, start_lon, 
           start_lan, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  group_by(start_station, weekend, time_of_day, start_lon, start_lan) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = LACensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = start_lon, y = start_lan, color = MAE), 
             fill = "transparent", size = 0.5, alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$start_lan), max(dat_census$start_lan))+
  xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors, Test Set",
         caption = "Figure 5-4")+
  mapTheme

5.3 Socio-economic variables

One might think that the station locations probably relate to likely users, who seem to be commuting downtown to the loop. Figure 5-4 plots MAE by three socio-economic variables: median income, percent taking public transportation and percent of white. Several stations show resistance to the models - they have high income, low public transportation usage and more than a half of white population.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lan = map(data, pull, start_lan), 
           start_lon = map(data, pull, start_lon),
           dotw = map(data, pull, dotw),
           Percent_Taking_Public_Trans = map(data, pull, Percent_Taking_Public_Trans),
           Med_Inc = map(data, pull, Med_Inc),
           Percent_White = map(data, pull, Percent_White)) %>%
    select(interval60, start_station, start_lon, 
           start_lan, Observed, Prediction, Regression,
           dotw, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  filter(time_of_day == "AM Rush") %>%
  group_by(start_station, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  gather(-start_station, -MAE, key = "variable", value = "value")%>%
  ggplot(.)+
  #geom_sf(data = LACensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = value, y = MAE), alpha = 0.4)+
  geom_smooth(aes(x = value, y = MAE), method = "lm", se= FALSE)+
  facet_wrap(~variable, scales = "free")+
  labs(title="Errors as a function of socio-economic variables",
       y="Mean Absolute Error (Trips)",
         caption = "Figure 5-4")+
  plotTheme

6 Cross-Validation

Below, a parameter called fitControl is set to specify the number of k-fold prtitions - in this case 100. In the code below, set.seed ensures reproducible folds. An object reg.cv, is estimated using the same regression as specified in reg 4.

fitControl <- trainControl(method = "cv", number = 100)
set.seed(8888)

reg.cv <- 
  train(Trip_Count ~  start_station +  hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day, 
     data=ride.panel, 
     method = "lm", trControl = fitControl, na.action = na.pass)

reg.cv
## Linear Regression 
## 
## 172912 samples
##     10 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (100 fold) 
## Summary of sample sizes: 171183, 171183, 171183, 171183, 171183, 171183, ... 
## Resampling results:
## 
##   RMSE      Rsquared   MAE      
##   0.497709  0.2339435  0.2295348
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE

The cross-validation output provides very important goodness of fit information. The resample shows the first five outputs of 100 folds.

reg.cv$resample[1:5,]
##        RMSE   Rsquared       MAE Resample
## 1 0.4378364 0.24580020 0.2218254  Fold001
## 2 0.4857485 0.34633998 0.2280337  Fold002
## 3 0.4930859 0.20575242 0.2330104  Fold003
## 4 0.4642082 0.17568579 0.2326678  Fold004
## 5 0.4936677 0.06945884 0.2304217  Fold005
MAE <- reg.cv$resample[,3]
df <- data.frame(MAE)

ggplot(df, aes(x=MAE)) +
  geom_histogram(aes(y=..density..),bins=35, fill="#FB8C6F") +
  labs(
    x="Cross-Validation MAE Histogram",
    y="MAE",
    caption = "Figure 6-1"
  )

plotTheme

7 Conclusion

I began by exploring serial autocorrelation and spatial autocorrelation of the LA Metro Bike Share trip data. Bike share counts Data by station shows certain time patterns during one week or one day: bikes are highly used on weekends and less on Wednesdays and Thursdays, 12:00-17:00 every day is the peak usage period. Bike share counts also shows spatial agglomeration, that is certain stations tend to have higher bike usages.

Then, five regression models are built. Divide the first three weeks as the training set, and the next two weeks as the test set. By calculating the prediction and error data of the model in the test set, the accuracy can be examined that the errors are relatively small.

LS0tCnRpdGxlOiAiQmlrZSBzaGFyZSIKYXV0aG9yOiAiWWluZ3h1ZSBPdSIKZGF0ZTogIjIwMjItMTEtMTEiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCiMgMSBJbnRyb2R1Y3Rpb24KClRoZSByZXF1aXJlbWVudCB0byAicmUtYmFsYW5jZSIgYmljeWNsZXMgdGhyb3VnaG91dCB0aGUgbmV0d29yayBpcyBvbmUgb2YgdGhlIGRpZmZpY3VsdCBvcGVyYXRpb25hbCBpc3N1ZXMgZmFjZWQgYnkgdXJiYW4gYmlrZSBzaGFyZSBzeXN0ZW1zLiBJZiB0aGVyZSBhcmUgbm8gYmlrZXMgYXZhaWxhYmxlIGZvciBwaWNrdXAgYXQgYSBkb2NrIG9yIGF2YWlsYWJsZSBkb2NraW5nIHNwb3RzIHRvIGxlYXZlIGEgYmlrZSwgYmlrZSBzaGFyZSBpcyB1c2VsZXNzLiBSZS1iYWxhbmNpbmcgaXMgdGhlIGFjdGl2aXR5IG9mIHBoeXNpY2FsbHkgcmVkaXN0cmlidXRpbmcgYmlrZXMgdG8gZW5zdXJlIHRoYXQgYSBiaWtlIG9yIGEgZG9ja2luZyBsb2NhdGlvbiBpcyBhY2Nlc3NpYmxlIHdoZW4gbmVlZGVkIHdoaWxlIGFudGljaXBhdGluZyAob3IgcHJvamVjdGluZykgYmlrZSBzaGFyaW5nIGRlbWFuZCBmb3IgYWxsIGRvY2tzIGF0IGFsbCB0aW1lcy4KClJlYmFsYW5jaW5nIGNhbiBiZSBhY2hpZXZlZCBieSBtdWx0aXBsZSB3YXlzLCB0cmFuc3BvcnRpbmcgZXhjZXNzIGJpa2VzIHRvIHdoZXJlIHRoZXkgYXJlIG5lZWRlZCBieSB0cnVja3MgbWlnaHQgYmUgb25lIG9mIHRoZSBtb3N0IG9wZXJhdGlvbmFsIGFuZCBlZmZpY2llbnQgd2F5cy4gT2YgY291cnNlLCBpdCBpcyBhbHNvIGEgZmVhc2libGUgd2F5IHRvIGVuY291cmFnZSBjeWNsaXN0cyB0byBwYXJrIHRoZWlyIGJpY3ljbGVzIGluIGRlc2lnbmF0ZWQgbG9jYXRpb25zIHRocm91Z2ggYXBwcm9wcmlhdGUgaW5jZW50aXZlcy4gQWxsIGluIGFsbCwgYWxtb3N0IGFsbCByZWJhbGFuY2luZyBtZXRob2RzIGFyZSBoaWdobHkgZGVwZW5kZW50IG9uIGFjY3VyYXRlIGFuYWx5c2lzIGFuZCBwcmVkaWN0aW9uIG9mIHRoZSBzcGF0aWFsIGFuZCB0aW1lIGRpc3RyaWJ1dGlvbiBvZiBiaWtlcy4gSW4gdGhpcyBwcm9qZWN0LCB0aGUgTEEgTWV0cm8gQmlrZSBTaGFyZSB0cmlwIGRhdGEgKHNvdXJjZTpodHRwczovL2Jpa2VzaGFyZS5tZXRyby5uZXQvYWJvdXQvZGF0YS8pIGZvciAyMDIyIFEyIHdpbGwgYmUgdXNlZCB0byBidWlsZCBtb2RlbHMgYW5kIGZvcmVjYXN0IHNwYWNlL3RpbWUgZGVtYW5kIGZvciBiaWtlIHNoYXJlIHVzYWdlcy4gCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMgMiBTZXQgdXAKCkZpcnN0IG9mIGFsbCwgc3RhcnQgd2l0aCB0aGUgcmVxdWlyZWQgbGlicmFyaWVzIGFuZCBmdW5jdGlvbnMuIAoKYGBge3Igc2V0dXBfMTIsIGNhY2hlPVRSVUUsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeSh0aWdyaXMpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHJpZW0pCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoUlNvY3JhdGEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnaWZza2kpCmxpYnJhcnkoZ2dhbmltYXRlKQpsaWJyYXJ5KGNhcmV0KQpwbG90VGhlbWUgPC0gdGhlbWUoCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rCiAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgcGxvdC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwKICAjcGFuZWwuYm9yZGVyPWVsZW1lbnRfcmVjdChjb2xvdXI9IiNGMEYwRjAiKSwKICAjIEZvcm1hdCB0aGUgZ3JpZAogIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91cj0iI0QwRDBEMCIsc2l6ZT0uMiksCiAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCkpCm1hcFRoZW1lIDwtIHRoZW1lKHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgICAgICAgICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogICAgICAgICAgICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBwYW5lbC5ib3JkZXI9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfbGluZShjb2xvdXIgPSAndHJhbnNwYXJlbnQnKSwKICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiLCAKICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwKICAgICAgICAgICAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMSwgMSwgMSwgMSwgJ2NtJyksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgxLCAiY20iKSwgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMC4yLCAiY20iKSkKcGFsZXR0ZTUgPC0gYygiI0ZCOEM2RiIsIiM3MzYwN0QiLCIjQzFCOUFFIiwiI0ZEQzY2NCIsIiMzNTVDN0QiKQpwYWxldHRlNCA8LSBjKCIjRkI4QzZGIiwiIzczNjA3RCIsIiNDMUI5QUUiLCIjRkRDNjY0IikKcGFsZXR0ZTIgPC0gYygiI0ZCOEM2RiIsIiM3MzYwN0QiKQpgYGAKCmBgYHtyIGluc3RhbGxfY2Vuc3VzX0FQSV9rZXksIHdhcm5pbmcgPSBGQUxTRSwgaW5jbHVkZT1GQUxTRSwgZXZhbCA9IFRSVUV9CiMgSW5zdGFsbCBDZW5zdXMgQVBJIEtleQp0aWR5Y2Vuc3VzOjpjZW5zdXNfYXBpX2tleSgiYTEyNDQ5NjNjMjE5MTMyZjMyM2I2ZmVmNzVkMzBiYTRkOTM5ZDFjNSIsIG92ZXJ3cml0ZSA9IFRSVUUpCmBgYAoKIyMgMi4xIEltcG9ydCBiaWtlIHNoYXJlIGRhdGEKClRoZSBMQSBNZXRybyBCaWtlIFNoYXJlIHRyaXAgZGF0YSAoc291cmNlOmh0dHBzOi8vYmlrZXNoYXJlLm1ldHJvLm5ldC9hYm91dC9kYXRhLykgZm9yIDIwMjIgUTIgaXMgcmVhZCBpbi4KCmBgYHtyIHJlYWRfZGF0ICwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpkYXQgPC0gc3RfcmVhZCgiLi9tZXRyby10cmlwcy0yMDIyLXEyLmNzdiIpIApzdGF0aW9uX25hbWUgPC0gc3RfcmVhZCgiLi9tZXRyby1iaWtlLXNoYXJlLXN0YXRpb25zLTIwMjItMTAtMDEuY3N2IikKCmNvbG5hbWVzKHN0YXRpb25fbmFtZSlbY29sbmFtZXMoc3RhdGlvbl9uYW1lKSA9PSAnU3RhdGlvbl9JRCddIDwtICJzdGFydF9zdGF0aW9uIgoKZGF0IDwtCiAgbWVyZ2UoZGF0LCBzdGF0aW9uX25hbWVbYygiU3RhdGlvbl9OYW1lIiwgInN0YXJ0X3N0YXRpb24iKV0sIGJ5ID0gInN0YXJ0X3N0YXRpb24iKQojICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyMjg2JykKYGBgCgpBcyBiZWxvdywgYHBhcnNlX2RhdGVfdGltZWAgZnVuY3Rpb24gaXMgdXNlZCB0byBzdGFuZGFyZGl6ZSB0aGUgYHN0YXJ0X3RpbWVgIGZpZWxkICh3aGVuIGEgdHJpcCBkZXBhcnRlZCkgaW50byB0aGUgMSBob3VyIGFuZCAxNSBtaW51dGUgaW50ZXJ2YWxzIG5lZWRlZCBmb3IgbGF0ZXIgYW5hbHlzaXMuIEZ1bmN0aW9uIGB3ZWVrYCBhbmQgYHdkYXlgIGNvbnZlcnQgdGhlIGRhdGEvdGltZSBzdGFtcCBpbnRvIHdlZWsgb2YgdGhlIHllYXIgYW5kIGRheSBvZiB0aGUgd2VlaywgcmVzcGVjdGl2ZWx5LgoKYGBge3IgdGltZV9iaW5zIH0KZGF0MiA8LSBkYXQgJT4lCiAgbXV0YXRlKGludGVydmFsNjAgPSBmbG9vcl9kYXRlKG1keV9obShzdGFydF90aW1lKSwgdW5pdCA9ICJob3VyIiksCiAgICAgICAgIGludGVydmFsMTUgPSBmbG9vcl9kYXRlKG1keV9obShzdGFydF90aW1lKSwgdW5pdCA9ICIxNSBtaW5zIiksCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgbXV0YXRlKG5ld19zdGFydF90aW1lID0gc3RycHRpbWUoYXMuY2hhcmFjdGVyKHN0YXJ0X3RpbWUpLCAiJW0vJWQvJVkgJUg6JU0iKSwKICAgICAgICAgbmV3X2VuZF90aW1lID0gc3RycHRpbWUoYXMuY2hhcmFjdGVyKGVuZF90aW1lKSwgIiVtLyVkLyVZICVIOiVNIikpICU+JQogIGZpbHRlcih3ZWVrPjE3ICYgd2VlazwyMykKZ2xpbXBzZShkYXQyKQpgYGAKCiMjIDIuMiBJbXBvcnQgQ2Vuc3VzIEluZm8KClRoZSBjb2RlIGJsb2NrIGJlbG93IHB1bGxzIGNlbnN1cyBnZW9ncmFwaHkgYW5kIHNvbWUgdmFyaWFibGVzIGZyb20gdGhlIGB0aWR5Y2Vuc3VzYCBwYWNrYWdlLiBUaGVzZSB3aWxsIGJlIHVzZWQgdG8gZGVtb25zdHJhdGUgZ2VuZXJhbGl6YWJpbGl0eSBsYXRlciwgYnV0IHdvbid0IGJlIHVzZWQgYXMgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGJlY2F1c2UgdGhleSBlbmQgdXAgYmVpbmcgcGVyZmVjdGx5IGNvbGluZWFyIHdpdGggdGhlIHN0YXRpb25zIGZpeGVkIGVmZmVjdHMuIFNwYXRpYWwgaW5mb3JtYXRpb24gd2lsbCBiZSBhZGRlZCB0byByaWRlcnNoYXJlIGRhdGEgYXMgb3JpZ2luIGFuZCBkZXN0aW5hdGlvbiBkYXRhLgoKYGBge3IgZ2V0X2NlbnN1cywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30KTEFDZW5zdXMgPC0gCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMTAwM18wMDEiLCAiQjE5MDEzXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsICJCMDgwMTNfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MzAxXzAxMCIsICJCMDEwMDJfMDAxIiksIAogICAgICAgICAgeWVhciA9IDIwMjAsIAogICAgICAgICAgc3RhdGUgPSAiQ0EiLCAKICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwgCiAgICAgICAgICBjb3VudHk9YygiTG9zIEFuZ2VsZXMiKSwKICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwKICAgICAgICAgTWVkX0luYyA9IEIxOTAxM18wMDFFLAogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLAogICAgICAgICBUcmF2ZWxfVGltZSA9IEIwODAxM18wMDFFLAogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLAogICAgICAgICBUb3RhbF9QdWJsaWNfVHJhbnMgPSBCMDgzMDFfMDEwRSkgJT4lCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwKICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIE1lZF9BZ2UsCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsCiAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkKYGBgCgpgYGB7ciBleHRyYWN0X2dlb21ldHJpZXMgfQpMQVRyYWN0cyA8LSAKICBMQUNlbnN1cyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIHNlbGVjdChHRU9JRCwgZ2VvbWV0cnkpICU+JSAKICBzdF9zZgpgYGAKCmBgYHtyIGFkZF9jZW5zdXNfdHJhY3RzICwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CgpkYXQyJHN0YXJ0X2xvbltkYXQyJHN0YXJ0X2xvbj09JyddIDwtIE5BCmRhdDIkc3RhcnRfbGF0W2RhdDIkc3RhcnRfbGF0PT0nJ10gPC0gTkEKZGF0MiRlbmRfbG9uW2RhdDIkZW5kX2xvbj09JyddIDwtIE5BCmRhdDIkZW5kX2xhdFtkYXQyJGVuZF9sYXQ9PScnXSA8LSBOQQoKZGF0X2NlbnN1cyA8LSAgc3Rfam9pbihkYXQyICU+JSAKICAgICAgICAgIGZpbHRlcihpcy5uYShzdGFydF9sb24pID09IEZBTFNFICYKICAgICAgICAgICAgICAgICAgIGlzLm5hKHN0YXJ0X2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xvbikgPT0gRkFMU0UgKSU+JQogICAgICAgICAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygic3RhcnRfbG9uIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KSwKICAgICAgICBMQVRyYWN0cyAlPiUKICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9NDMyNiksCiAgICAgICAgam9pbj1zdF9pbnRlcnNlY3RzLAogICAgICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoT3JpZ2luLlRyYWN0ID0gR0VPSUQpICU+JQogIG11dGF0ZShzdGFydF9sb24gPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAxKSksCiAgICAgICAgIHN0YXJ0X2xhbiA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KC1nZW9tZXRyeSklPiUKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJlbmRfbG9uIiwgImVuZF9sYXQiKSwgY3JzID0gNDMyNikgJT4lCiAgc3Rfam9pbiguLCBMQVRyYWN0cyAlPiUKICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwKICAgICAgICAgIGpvaW49c3RfaW50ZXJzZWN0cywKICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoRGVzdGluYXRpb24uVHJhY3QgPSBHRU9JRCkgICU+JQogIG11dGF0ZShlbmRfbG9uID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLAogICAgICAgICBlbmRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBzZWxlY3QoLWdlb21ldHJ5KQpgYGAKCiMjIDIuMyBJbXBvcnQgV2VhdGhlciBEYXRhCgpJdCB3b3VsZCBiZSByZWFzb25hYmxlIHRvIGFudGljaXBhdGUgdGhhdCBiYWQgd2VhdGhlciBpbiB0aGUgV2luZHkgQ2l0eSB3b3VsZCBlbmNvdXJhZ2UgcmlkZSBzaGFyaW5nLiBIZXJlLCBJIGltcG9ydGVkIHdlYXRoZXIgZGF0YSBmcm9tIExvcyBBbmdlbGVzIERvd250b3duL1VTQyAoU0lEIGNvZGU6IENRVCwgc2VydmluZyBwZXJpb2QgZnJvbSAxOTk5IHRpbGwgbm93KSB1c2luZyBgcmllbV9tZWFzdXJlc2AgYmV0d2VlbiAyMDIyLTA1LTAxIGFuZCAyMDIyLTA1LTMxLgoKQmVsb3cgYSBgd2VhdGhlci5QYW5lbGAgaXMgZ2VuZXJhdGVkIHRvIHN1bW1hcml6ZSB0ZW1wZXJhdHVyZSwgcHJlY2lwaXRhdGlvbiBhbmQgd2luZCBzcGVlZCBmb3IgZXZlcnkgaG91ciBpbiBNYXkgMjAyMi4KCmBgYHtyIHdlYXRoZXJ9CndlYXRoZXIuUGFuZWwgPC0gCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIkNRVCIsIGRhdGVfc3RhcnQgPSAiMjAyMi0wNC0zMCIsIGRhdGVfZW5kID0gIjIwMjItMDYtMDMiKSAlPiUKICBkcGx5cjo6c2VsZWN0KHZhbGlkLCB0bXBmLCBwMDFpLCBza250KSU+JQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUKICAgIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShUZW1wZXJhdHVyZSA9IG1heCh0bXBmKSwKICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLAogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQogICAgbXV0YXRlKFRlbXBlcmF0dXJlID0gaWZlbHNlKFRlbXBlcmF0dXJlID09IDAsIDQyLCBUZW1wZXJhdHVyZSkpCgpnbGltcHNlKHdlYXRoZXIuUGFuZWwpCmBgYAoKQmVsb3cgdGhlIHdlYXRoZXIgZGF0YSBpcyBwbG90dGVkIGFzIGEgdGltZSBzZXJpZXMgdXNpbmcgYGdyaWQuYXJyYW5nZWAuCgpgYGB7ciBwbG90X3dlYXRoZXIsIGNhdGNoZSA9IFRSVUV9CmdyaWQuYXJyYW5nZSgKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsUHJlY2lwaXRhdGlvbikpICsgZ2VvbV9saW5lKCkgKyAKICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyBwbG90VGhlbWUsCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFdpbmRfU3BlZWQpKSArIGdlb21fbGluZSgpICsgCiAgICBsYWJzKHRpdGxlPSJXaW5kIFNwZWVkIiwgeD0iSG91ciIsIHk9IldpbmQgU3BlZWQiKSArIHBsb3RUaGVtZSwKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsVGVtcGVyYXR1cmUpKSArIGdlb21fbGluZSgpICsgCiAgICBsYWJzKHRpdGxlPSJUZW1wZXJhdHVyZSIsIHg9IkhvdXIiLCB5PSJUZW1wZXJhdHVyZSIpICsgcGxvdFRoZW1lLAogIHRvcD0iV2VhdGhlciBEYXRhIC0gTEEgQ1FUIC0gTWF5LCAyMDIyIikKYGBgCgojIDMgRGVzY3JpYmUgYW5kIEV4cGxvcmUgdGhlIERhdGEKCiMjIDMuMSBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbAoKVG8gbWFrZSBzdXJlIHRoYXQgZWFjaCB1bmlxdWUgYW5kIGhvdXIvZGF5IGNvbWJvIGV4aXN0cyBpbiBvdXIgZGF0YSBzZXQsIGFuIGVtcHR5IGBzdHVkeS5wYW5lbGAgaXMgY3JlYXRlZCB3aGljaCBoYXMgZWFjaCB1bmlxdWUgc3BhY2UvdGltZSBvYnNlcnZhdGlvbnMuIE5vIG1hdHRlciBhbiBvYnNlcnZhdGlvbiB0b29rIHBsYWNlIG9yIG5vdCwgZWFjaCB0aW1lIHBlcmlvZCBpbiB0aGUgc3R1ZHkgaXMgcmVwcmVzZW50ZWQgYnkgYSByb3cgaW4gaXQuIAoKVGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbWJpbmF0aW9ucyBjYW4gYmUgY2FsY3VsYXRlZCBieSAyNCBob3VycyBwZXIgZGF5ICogNyBkYXlzIHBlciB3ZWVrICogNSB3ZWVrcyAqIHRoZSBudW1iZXIgb2Ygc3RhdGlvbnMuIFRoZW4sIGNvbXBhcmUgdGhhdCB0byB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gYHN0dWR5LnBhbmVsYCBieSBgbnJvd2AuIEJvdGggc3RlcHMgc2hvdyB0aGUgc2FtZSByZXN1bHQ6IDE3NDUyOCwgd2hpY2ggbWVhbnMgdGhhdCB0aGUgY291bnRzIGlzIGNvcnJlY3QuCgpDb250aW51ZSB0byBqb2luIHRoZSBzdGF0aW9uIG5hbWUsIHRyYWN0IGFuZCBsYXRpdHVkZS9sb25naXR1ZGUuCgpgYGB7ciBwYW5lbF9sZW5ndGhfY2hlY2sgLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbGVuZ3RoKHVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApKSAqIGxlbmd0aCh1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uKSkKCnN0dWR5LnBhbmVsIDwtIAogIGV4cGFuZC5ncmlkKGludGVydmFsNjA9dW5pcXVlKGRhdF9jZW5zdXMkaW50ZXJ2YWw2MCksIAogICAgICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSB1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uKSkgJT4lCiAgbGVmdF9qb2luKC4sIGRhdF9jZW5zdXMgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KHN0YXJ0X3N0YXRpb24sIFN0YXRpb25fTmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydF9sb24sIHN0YXJ0X2xhbiApJT4lIAogICAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbikgJT4lCiAgICAgICAgICAgICAgc2xpY2UoMSkpCm5yb3coc3R1ZHkucGFuZWwpICAgICAgCmBgYAoKSW4gdGhlIGNvZGUgYmVsb3csIGEgZnVsbCBwYW5lbCBpcyBzdW1tYXJpemluZyBieSBzdGF0aW9uIGZvciBlYWNoIHRpbWUgaW50ZXJ2YWwsIGtlZXAgY2Fuc3VzIGluZm8gYW5kIGxhdGl0dWRlL2xvbmdpdHVkZSBpbmZvcm1hdGlvbiBhbG9uZyBmb3Igam9pbmluZyBsYXRlciB0byBvdGhlciBkYXRhLiBTdGFydCBzdGF0aW9uIElEcyBhcmUgcmVtb3ZlZCBieSBgRkFMU0VgLgoKYGBge3IgY3JlYXRlX3BhbmVsICwgbWVzc2FnZSA9IEZBTFNFfQpyaWRlLnBhbmVsIDwtIAogIGRhdF9jZW5zdXMgJT4lCiAgbXV0YXRlKFRyaXBfQ291bnRlciA9IDEpICU+JQogIHJpZ2h0X2pvaW4oc3R1ZHkucGFuZWwpICU+JSAKICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uLCBTdGF0aW9uX05hbWUsIE9yaWdpbi5UcmFjdCwgc3RhcnRfbG9uLCBzdGFydF9sYW4pICU+JSAjIG1pc3NpbmcgImZyb21fc3RhdGlvbl9uYW1lIgogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnRlciwgbmEucm09VCkpICU+JQogIGxlZnRfam9pbih3ZWF0aGVyLlBhbmVsKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZmlsdGVyKGlzLm5hKHN0YXJ0X3N0YXRpb24pID09IEZBTFNFKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksCiAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSkpICU+JQogIGZpbHRlcihpcy5uYShPcmlnaW4uVHJhY3QpID09IEZBTFNFKQpgYGAKCmBgYHtyIGNlbnN1c19hbmRfcGFuZWwgLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUucGFuZWwgPC0gCiAgbGVmdF9qb2luKHJpZGUucGFuZWwsIExBQ2Vuc3VzICU+JQogICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5KSwgYnkgPSBjKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpCmBgYAoKIyMgMy4yIENyZWF0ZSB0aW1lIGxhZ3MKCmBgYHtyIHRpbWVfbGFncyAsIG1lc3NhZ2UgPSBGQUxTRX0KcmlkZS5wYW5lbCA8LSAKICByaWRlLnBhbmVsICU+JSAKICBhcnJhbmdlKHN0YXJ0X3N0YXRpb24sIGludGVydmFsNjApICU+JSAKICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwKICAgICAgICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDIpLAogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwKICAgICAgICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxMiksCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpLAogICAgICAgICBob2xpZGF5ID0gaWZlbHNlKHlkYXkoaW50ZXJ2YWw2MCkgPT0gMTQ4LDEsMCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lCiAgIG11dGF0ZShob2xpZGF5TGFnID0gY2FzZV93aGVuKGRwbHlyOjpsYWcoaG9saWRheSwgMSkgPT0gMSB+ICJQbHVzT25lRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmxhZyhob2xpZGF5LCAyKSA9PSAxIH4gIlBsdXN0VHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMykgPT0gMSB+ICJQbHVzdFRocmVlRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDEpID09IDEgfiAiTWludXNPbmVEYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAyKSA9PSAxIH4gIk1pbnVzVHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwKICAgICAgICAgaG9saWRheUxhZyA9IGlmZWxzZShpcy5uYShob2xpZGF5TGFnKSA9PSBUUlVFLCAwLCBob2xpZGF5TGFnKSkKYGBgCgpgYGB7ciBldmFsdWF0ZV9sYWdzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgICBzdW1tYXJpc2VfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtaW50ZXJ2YWw2MCwgLVRyaXBfQ291bnQpICU+JQogICAgbXV0YXRlKFZhcmlhYmxlID0gZmFjdG9yKFZhcmlhYmxlLCBsZXZlbHM9YygibGFnSG91ciIsImxhZzJIb3VycyIsImxhZzNIb3VycyIsImxhZzRIb3VycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsYWcxMkhvdXJzIiwibGFnMWRheSIpKSklPiUKICAgIGdyb3VwX2J5KFZhcmlhYmxlKSAlPiUgIAogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gcm91bmQoY29yKFZhbHVlLCBUcmlwX0NvdW50KSwyKSkKYGBgCgojIyAzLjMgVHJhbmluZy90ZXN0aW5nIGRhdGEgc2V0CgpIZXJlLCBhIHRpbWUgc2VyaWVzIGFwcHJvYWNoIGlzIHRha2VuLCBhIHRyYWluaW5nIGRhdGEgc2V0IHdpdGggMyB3ZWVrcyBvZiBkYXRhICh3ZWVrcyAxOC0yMCkgYW5kIGEgdGVzdGluZyBkYXRhIHNldCB3aXRoIDIgd2Vla3Mgb2YgZGF0YSAod2Vla3MgMjEtMjIpIGFyZSBjcmVhdGVkLiBDb2RlIGJlbG93IHNwbGl0IHRoZSBkYXRhIGJ5IGB3ZWVrYC4KCmBgYHtyIHRyYWluX3Rlc3QgfQpyaWRlLlRyYWluIDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID49IDE4ICYgd2VlayA8PSAyMCkKcmlkZS5UZXN0IDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID49MjEgJiB3ZWVrIDw9IDIyKQpgYGAKCiMjIDMuNCBFeHBsb3JhdG9yaW5nIGRhdGEKCkluIHRoaXMgc2VjdGlvbiwgdGhlIHJpZGVyIHNoYXJlIGRhdGEgaXMgZXhwbG9yZWQgZm9yIHRpbWUsIHNwYWNlLCB3ZWF0aGVyLCBhbmQgZGVtb2dyYXBoaWMgcmVsYXRpb25zaGlwcy4gSWYgdGhlIGBUcmlwX0NvdW50YCBleGhpYml0IHNlcmlhbCh0ZW1wb3JhbCkgY29ycmVsYXRpb24sIHRoZSB0aW1lIGxhZyBmZWF0dXJlcyB3aWxsIGxlYWQgdG8gYmV0dGVyIHByZWRpY3Rpb25zLgoKIyMjIDMuNC4xIFNlcmlhbCBhdXRvY29ycmVsYXRpb24KCldlIGJlZ2luIGJ5IGV4YW1pbmluZyB0aGUgdGltZSBhbmQgZnJlcXVlbmN5IGNvbXBvbmVudHMgb2Ygb3VyIGRhdGEuCgpGaXJzdCwgd2UgbG9vayBhdCB0aGUgb3ZlcmFsbCB0aW1lIHBhdHRlcm4gLSB0aGVyZSBpcyBjbGVhcmx5IGEgZGFpbHkgcGVyaW9kaWNpdHkgYW5kIHRoZXJlIGFyZSBsdWxsIHBlcmlvZHMgb24gd2Vla2VuZHMuIGBnZW8tdmxpbmVgIGlzIHVzZWQgdG8gdmlzdWFsaXplIGBNb25kYXlzYC4gQWxsIG9mIHRoZSBmaXZlIHdlZWtzIGV4aGliaXQgc2ltaWxhciB0aW1lIHNlcmllcyBwYXR0ZXJucyB3aXRoIGNvbnNpc3RlbnQgcGVha3MgYW5kIHRyb3VnaHMuIFdoaWNoIHN1Z2dlc3RzIHRoZSBwcmVzZW5jZSBvZiBzZXJpYWwgY29ycmVsYXRpb24uCgpgYGB7ciB0cmlwX3RpbWVzZXJpZXMscmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQptb25kYXlzIDwtIAogIG11dGF0ZShyaWRlLnBhbmVsLAogICAgICAgICBtb25kYXkgPSBpZmVsc2UoZG90dyA9PSAiTW9uIiAmIGhvdXIoaW50ZXJ2YWw2MCkgPT0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgIGludGVydmFsNjAsIDApKSAlPiUKICBmaWx0ZXIobW9uZGF5ICE9IDApIAoKc3RfZHJvcF9nZW9tZXRyeShyYmluZCgKICBtdXRhdGUocmlkZS5UcmFpbiwgTGVnZW5kID0gIlRyYWluaW5nIiksIAogIG11dGF0ZShyaWRlLlRlc3QsIExlZ2VuZCA9ICJUZXN0aW5nIikpKSAlPiUKICAgIGdyb3VwX2J5KExlZ2VuZCwgaW50ZXJ2YWw2MCkgJT4lIAogICAgICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50KSkgJT4lCiAgICAgIHVuZ3JvdXAoKSAlPiUgCiAgICAgIGdncGxvdChhZXMoaW50ZXJ2YWw2MCwgVHJpcF9Db3VudCwgY29sb3VyID0gTGVnZW5kKSkgKyBnZW9tX2xpbmUoKSArCiAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlMikgKwogICAgICAgIGdlb21fdmxpbmUoZGF0YSA9IG1vbmRheXMsIGFlcyh4aW50ZXJjZXB0ID0gbW9uZGF5KSkgKwogICAgICAgIGxhYnModGl0bGU9IkxBIFJpZGVzaGFyZSB0cmlwcyBieSB3ZWVrOiBBcHIgMzAgLSBKdW4gMDMsIDIwMjIiLAogICAgICAgICAgICAgc3VidGl0bGU9IkRvdHRlZCBsaW5lcyBmb3IgTW9uZGF5cyIsIAogICAgICAgICAgICAgeD0iRGF5IiwgeT0iVHJpcCBDb3VudCIsCiAgICAgICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTEiKSArCiAgICAgICAgcGxvdFRoZW1lICsgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSkgICAgCmBgYAoKTmV4dCwgdGhlIHRpbWUgYGxhZ2AgZmVhdHVyZXMgYXJlIHRlc3RlZCBmb3IgY29ycmVsYXRpb24gd2l0aCBgVHJpcF9Db3VudGAuIGBwbG90RGF0YS5sYWdgIHJldHVybnMgdGhlIGBUcmlwX0NvdW50YCBhbmQgdGltZSBsYWcgZmVhdHVyZXMgZm9yIHdlZWsgMTguIGBmY3RfcmVsZXZlbGAgcmVvcmRlcnMgdGhlIGxhZyBsZXZlbHMuIE9taXQgdGhhdCBsaW5lIGFuZCB0cnkgYGxldmVscyhwbG90RGF0YS5sYWckVmFyaWFibGUpYC4KClBlYXJzb24gY29ycmVsYXRpb24gaXMgdGhlbiBjYWxjdWxhdGVkIGZvciBlYWNoIGBWYXJpYWJsZWAgaW4gYGNvcnJlbGF0aW9uLmxhZ2AuCgpXZWFrIGBUcmlwX0NvdW50YCBjb3JyZWxhdGlvbnMgYXJlIHZpc3VhbGl6ZWQgYmVsb3cuCgpgYGB7ciB0aW1lX2xhZywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQoKcGxvdERhdGEubGFnIDwtCiAgZmlsdGVyKGFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCksIHdlZWsgPT0gMTgpICU+JQogIGRwbHlyOjpzZWxlY3Qoc3RhcnRzX3dpdGgoImxhZyIpLCBUcmlwX0NvdW50KSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtVHJpcF9Db3VudCkgJT4lCiAgbXV0YXRlKFZhcmlhYmxlID0gZmN0X3JlbGV2ZWwoVmFyaWFibGUsICJsYWdIb3VyIiwibGFnMkhvdXJzIiwibGFnM0hvdXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhZzRIb3VycyIsImxhZzEySG91cnMiLCJsYWcxZGF5IikpCmNvcnJlbGF0aW9uLmxhZyA8LQogIGdyb3VwX2J5KHBsb3REYXRhLmxhZywgVmFyaWFibGUpICU+JQogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gcm91bmQoY29yKFZhbHVlLCBUcmlwX0NvdW50LCB1c2UgPSAiY29tcGxldGUub2JzIiksIDIpKSAKCnBsb3REYXRhLmxhZyAlPiUKZ2dwbG90KCkrCiAgZ2VvbV9wb2ludChhZXMoeD0gVmFsdWUsIHkgPSBUcmlwX0NvdW50KSkrCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3JyZWxhdGlvbi5sYWcsIGFlcyhsYWJlbCA9IHBhc3RlKCJyID0iLCByb3VuZChjb3JyZWxhdGlvbiwgMikpKSwKICAgICAgICAgICAgeD0tSW5mLCB5PUluZiwgdmp1c3QgPSAxLjUsIGhqdXN0ID0gLS4xKSArCiAgZ2VvbV9zbW9vdGgoYWVzKHg9IFZhbHVlLCB5PSBUcmlwX0NvdW50KSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAiNzM2MDdEIikrCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSsKICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnModGl0bGU9IlJpZGVyc2hhcmUgdHJpcCBjb3VudCBhcyBhIGZ1bmN0aW9uIG9mIHRpbWUgbGFncyIsCiAgICAgICB4PSJMYWcgVHJpcCBDb3VudCIsIAogICAgICAgeT0iVHJpcF9Db3VudCIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTIiKSsKICBwbG90VGhlbWUKYGBgCgpGaWd1cmUgMy0zIGlsbHVzdHJhdGVzIHRoZSBkaXN0cmlidXRpb24gb2YgdHJpcCB2b2x1bWUgYnkgc3RhdGlvbiBmb3IgZGlmZmVyZW50IHRpbWVzIG9mIHRoZSBkYXkuIFRoZXJlIGFyZSBzb21lIHN0YXRpb25zIHdpdGggaGlnaCB2b2x1bWUgcGVyaW9kcywgYnV0IG1vcmUgaGF2ZSBsb3cgdm9sdW1lLiBUaGVyZWZvcmUsIG91ciBkYXRhIHdpbGwgY29uc2lzdCBvZiBhIG51bWJlciBvZiBsb3cgZGVtYW5kIHN0YXRpb24vaG91cnMgYW5kIGEgZmV3IGhpZ2ggZGVtYW5kIHN0YXRpb24gaG91cnMuCgpXZSBjYW4gYWxzbyB0cmFjayB0aGUgZGFpbHkgdHJlbmRzIGluIHJpZGVyc2hpcCBieSBkYXkgb2YgdGhlIHdlZWsgYW5kIHdlZWtlbmQgdmVyc3VzIHdlZWtkYXksIHRvIHNlZSB3aGF0IHRlbXBvcmFsIHBhdHRlcm5zIHdlJ2QgbGlrZSB0byBjb250cm9sIGZvci4KCmBgYHtyIG1lYW5fdHJpcHNfaGlzdCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQpkYXRfY2Vuc3VzICU+JQogICAgICAgIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUKICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgdGltZV9vZl9kYXkpICU+JQogICAgICAgICB0YWxseSgpJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgdGltZV9vZl9kYXkpJT4lCiAgc3VtbWFyaXplKG1lYW5fdHJpcHMgPSBtZWFuKG4pKSU+JQogIGdncGxvdCgpKwogIGdlb21faGlzdG9ncmFtKGFlcyhtZWFuX3RyaXBzKSwgYmlud2lkdGggPSAxKSsKICBsYWJzKHRpdGxlPSJNZWFuIE51bWJlciBvZiBIb3VybHkgVHJpcHMgUGVyIFN0YXRpb24uIExBLCBBcHIgMzAgLSBKdW4gMDMsIDIwMjIiLAogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgCiAgICAgICB5PSJGcmVxdWVuY3kiLAogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgMy0zIikrCiAgZmFjZXRfd3JhcCh+dGltZV9vZl9kYXkpKwogIHBsb3RUaGVtZQpgYGAKCmBgYHtyIHRyaXBzX3N0YXRpb25fZG90dyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uKSAlPiUKICAgICAgICAgdGFsbHkoKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG4pLCBiaW53aWR0aCA9IDUpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24uIExBLCBBcHIgMzAgLSBKdW4gMDMsIDIwMjIiLAogICAgICAgeD0iVHJpcCBDb3VudHMiLCAKICAgICAgIHk9Ik51bWJlciBvZiBTdGF0aW9ucyIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTQiKSsKICBwbG90VGhlbWUKYGBgCgpGaWd1cmUgMy01IGFuZCAzLTYgaWxsdXN0cmF0ZSB0b3RhbCBgVHJpcF9Db3VudHNgIGRpc3RyaWJ1dGlvbiBpbiBvbmUgZGF5IGJ5IHdlZWtkYXlzIGFuZCAgd2Vla2VuZHMuIEl0IGNsZWFyIHRoYXQgdGhlIHRyaXAgdm9sdW1lIG9mIGVhY2ggZGF5IGluIGEgd2VlayBoYXZlIHNpbWlsYXIgcGF0dGVybnM6IHJlYWNoaW5nIHBlYWsgYXQgYWJvdXQgNDAwIGluIDEyOjAwLTE3OjAwIGFuZCBmYWxsaW5nIGF0IG5lYXIgMCB2YWx1ZSBpbiAzOjAwIG9yIDI0OjAwLiBXZWVrZGF5cyBoYXZlIGFsbW9zdCBkb3VibGUgcGVhayB2b2x1bWUgdGhhbiB3ZWVrZW5kcy4KCmBgYHtyIHRyaXBzX2hvdXJfZG90dyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JSBtdXRhdGUoaG91ciA9IGhvdXIobmV3X3N0YXJ0X3RpbWUpKSkrCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSBkb3R3KSwgYmlud2lkdGggPSAxKSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIExBLCBieSBkYXkgb2YgdGhlIHdlZWssIE1heSwgMjAyMiIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTUiKSsKICAgICBwbG90VGhlbWUKZ2dwbG90KGRhdF9jZW5zdXMgJT4lIAogICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIobmV3X3N0YXJ0X3RpbWUpLAogICAgICAgICAgICAgICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IHdlZWtlbmQpLCBiaW53aWR0aCA9IDEpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgaW4gTEEgLSB3ZWVrZW5kIHZzIHdlZWtkYXksIEFwciAzMCAtIEp1biAwMywgMjAyMiIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTYiKSsKICAgICBwbG90VGhlbWUKYGBgCgojIyMgMy40LjIgU3BhdGlhbCBhdXRvY29ycmVsYXRpb24KCkluIHRoaXMgcGFydCwgdGhlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIHdpbGwgYmUgZXhhbWluZWQuIEZpZ3VyZSAzLTcgc2hvd3MgdGhlIGBUcmlwLUNvdW50YCBwZXIgaG91ciBieSBzdGF0aW9uIGluIHdlZWtkYXlzIGFuZCB3ZWVrZW5kcywgcmVzcGVjdGl2ZWx5LgoKYGBge3Igb3JpZ2luX21hcCB9CmdncGxvdCgpKwogIGdlb21fc2YoZGF0YSA9IExBVHJhY3RzICU+JQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSkrCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUgCiAgICAgICAgICAgIG11dGF0ZShob3VyID0gaG91cihuZXdfc3RhcnRfdGltZSksCiAgICAgICAgICAgICAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xhbiwgc3RhcnRfbG9uLCB3ZWVrZW5kLCB0aW1lX29mX2RheSkgJT4lCiAgICAgICAgICAgICAgdGFsbHkoKSwKICAgICAgICAgICAgYWVzKHg9c3RhcnRfbG9uLCB5ID0gc3RhcnRfbGFuLCBjb2xvciA9IG4pLCAKICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGFscGhhID0gMC41LCBzaXplID0gMC41KSsKICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwKICBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCIpKwogIHlsaW0obWluKGRhdF9jZW5zdXMkc3RhcnRfbGFuKSwgbWF4KGRhdF9jZW5zdXMkc3RhcnRfbGFuKSkrCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRzdGFydF9sb24pLCBtYXgoZGF0X2NlbnN1cyRzdGFydF9sb24pKSsKICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkrCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBwZXIgaHIgYnkgc3RhdGlvbi4gTEEsIE1heSwgMjAyMiIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTciKSsKICBtYXBUaGVtZQpgYGAKCiMjIyAzLjQuMyBTcGFjZS90aW1lIGNvcnJlbGF0aW9uCgpgYGB7ciB3ZWVrX3BhbmVsfQp3ZWVrMjAgPC0KICBmaWx0ZXIocmlkZS5wYW5lbCAsIHdlZWsgPT0gMjAgJiBkb3R3ID09ICJNb24iKQoKd2VlazIwLnBhbmVsIDwtCiAgZXhwYW5kLmdyaWQoCiAgICBpbnRlcnZhbDYwID0gdW5pcXVlKHdlZWsyMCRpbnRlcnZhbDYwKSwKICAgIHN0YXJ0X3N0YXRpb24gPSB1bmlxdWUocmlkZS5wYW5lbCRzdGFydF9zdGF0aW9uKSkKYGBgCgpgYGB7ciAgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpyaWRlLmFuaW1hdGlvbi5kYXRhIDwtCiAgbXV0YXRlKHdlZWsyMCwgVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGFuLCBUcmlwX0NvdW50ZXIpICU+JQogICAgcmlnaHRfam9pbih3ZWVrMjAucGFuZWwpICU+JSAKICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGFuLCkgJT4lCiAgICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUgCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUoVHJpcHMgPSBjYXNlX3doZW4oVHJpcF9Db3VudCA9PSAwIH4gIjAgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAwICYgVHJpcF9Db3VudCA8PSAyIH4gIjEtMiB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDIgJiBUcmlwX0NvdW50IDw9IDUgfiAiMy01IHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gNSAmIFRyaXBfQ291bnQgPD0gMTAgfiAiNi0xMCB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDEwICYgVHJpcF9Db3VudCA8PSAxNSB+ICIxMS0xNSB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDE1IH4gIjE2KyB0cmlwcyIpKSAlPiUKICAgIG11dGF0ZShUcmlwcyAgPSBmY3RfcmVsZXZlbChUcmlwcywgIjAgdHJpcHMiLCIxLTIgdHJpcHMiLCIzLTUgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiNi0xMCB0cmlwcyIsIjYtMTAgdHJpcHMiLCIxNisgdHJpcHMiKSkKYGBgCgpgYGB7ciAgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpyaWRlc2hhcmVfYW5pbWF0aW9uIDwtCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihkYXRhID0gTEFUcmFjdHMgJT4lCiAgICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gcmlkZS5hbmltYXRpb24uZGF0YSwgCiAgICAgICAgICAgICAgIGFlcyh4ID0gc3RhcnRfbG9uLCB5ID0gc3RhcnRfbGFuLCBmaWxsID0gVHJpcHMpLCBzaXplID0gMC41LCBhbHBoYSA9IDEuNSkrCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsKICAgIGxhYnModGl0bGUgPSAiUmlkZXNoYXJlIHBpY2t1cHMgZm9yIG9uZSBkYXkgaW4gTWF5IDIwMjIsIExBIiwKICAgICAgICAgc3VidGl0bGUgPSAiNjAgbWludXRlIGludGVydmFsczoge2N1cnJlbnRfZnJhbWV9IiwKICAgICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgMy04IikgKwogICAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWw2MCkgKwogICAgbWFwVGhlbWUKCmFuaW1hdGUocmlkZXNoYXJlX2FuaW1hdGlvbiwgZHVyYXRpb249NzUsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpCmBgYApgYGB7cn0KcmlkZXNoYXJlX2FuaW1hdGlvbiA8LQogIGdncGxvdCgpICsKICAgIGdlb21fc2YoZGF0YSA9IHJpZGUuYW5pbWF0aW9uLmRhdGEsIGFlcyhmaWxsID0gVHJpcHMpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNSkgKwogICAgbGFicyh0aXRsZSA9ICJSaWRlc2hhcmUgcGlja3VwcyBmb3Igb25lIGRheSBpbiBOb3ZlbWJlciAyMDE4IiwKICAgICAgICAgc3VidGl0bGUgPSAiMTUgbWludXRlIGludGVydmFsczoge2N1cnJlbnRfZnJhbWV9IikgKwogICAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWwxNSkgKwogICAgbWFwVGhlbWUKYGBgCgojIDQgTW9kZWxsaW5nCgojIyA0LjEgQnVpbGRpbmcgbW9kZWwKCkluIHRoaXMgc2VjdGlvbiwgZml2ZSBkaWZmZXJlbnQgbGluZWFyIHJlZ3Jlc3Npb25zIGFyZSBlc3RpbWF0ZWQgb24gYHJpZGUuVHJhaW5gLCBlYWNoIHdpdGggZGlmZmVyZW50IGZpeGVkIGVmZmVjdHM6CjEuIGByZWcxYCBmb2N1c2VzIG9uIGp1c3QgdGltZSwgaW5jbHVkaW5nIGhvdXIgZml4ZWQgZWZmZWN0cywgZGF5IG9mIHRoZSB3ZWVrLCBhbmQgYFRlbXBlcmF0dXJlYC4KMi4gYHJlZzJgIGZvY3VzZXMgb24ganVzdCBzcGFjZSBlZmZlY3RzIHdpdGggdGhlIGBzdGFydF9zdGF0aW9uYCBmaXhlZCBlZmZlY3RzLgozLiBgcmVnM2AgaW5jbHVkZXMgYm90aCB0aW1lIGFuZCBzcGFjZSBmaXhlZCBlZmZlY3RzLgo0LiBgcmVnNGAgYWRkcyB0aGUgdGltZSBgbGFnYCBmZWF0dXJlcy4KNS4gYHJlZzVgIGFkZHMgdGhlIGhvbGlkYXkgYGxhZ2AgZmVhdHVyZXMuCgpgYGB7ciBmaXZlX21vZGVscyB9CnJlZzEgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlLCAgZGF0YT1yaWRlLlRyYWluKQpyZWcyIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikKcmVnMyA8LSAKICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb24gKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiwgCiAgICAgZGF0YT1yaWRlLlRyYWluKQpyZWc0IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSwgCiAgICAgZGF0YT1yaWRlLlRyYWluKQpyZWc1IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArbGFnMTJIb3VycyArIGxhZzFkYXkgKyBob2xpZGF5LCAjICsgaG9saWRheUxhZyAsCiAgICAgZGF0YT1yaWRlLlRyYWluKQpgYGAKCiMjIDQuMiBQcmVkaWN0IGZvciB0ZXN0IGRhdGEKCk1vZGVscyBidWlsdCBhYm92ZSB3aWxsIGJlIHVzZWQgZm9yIHByZWRpY3RpbmcgZGF0YSBpbiBgcmlkZS5UZXN0YCB3aGljaCBpbmNsdWRlcyAyIHNmIHRpYmJsZXMgKHdpdGggZ2VvbWV0cmllcykuIAoKYGBge3IgbmVzdF9kYXRhICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUuVGVzdC53ZWVrTmVzdCA8LSAKICByaWRlLlRlc3QgJT4lCiAgbmVzdCgtd2VlaykgCgpyaWRlLlRlc3Qud2Vla05lc3QKYGBgCgpOZXh0LCBhIGZ1bmN0aW9uIGlzIGNyZWF0ZWQgdGhhdCB0YWtlcyBhIHRpYmJsZSwgYGRhdGAgYW5kIGEgcmVncmVzc2lvbiBtb2RlbCwgYGZpdGAgYXMgaXRzIGlucHV0cywgYW5kIG91dHB1dHMgcHJlZGljdGlvbnMgYXMgYHByZWRgLiBFYWNoIHdlZWsgaW4gYHJpZGUuVHJlc3Qud2Vla05lc3RgIGNhbiBiZSBwcmVkaWN0ZWQgYnkgdGhpcyBmdW5jdGlvbi4KCmBgYHtyIHByZWRpY3RfZnVuY3Rpb24gfQptb2RlbF9wcmVkIDwtIGZ1bmN0aW9uKGRhdCwgZml0KXsKICAgcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9CmBgYAoKVGhlIGNvZGUgYmVsb3cgd2lsbCBsb29wIHRocm91Z2ggZWFjaCBtb2RlbCBmb3IgZWFjaCB0ZXN0aW5nIHdlZWsgYW5kIG11dGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MsIGluY2x1ZGluZyB0aGUgcHJlZGljdGlvbnMsIG9ic2VydmVkLCBhYnNvbHV0ZSBlcnJvciwgTUFFIGFuZCBTRCBBRS4KCmBgYHtyIGRvX3ByZWRpY2l0b25zLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSAsIHdhcm5pbmc9RkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zIDwtIAogIHJpZGUuVGVzdC53ZWVrTmVzdCAlPiUgCiAgICBtdXRhdGUoQVRpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIEJTcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzIsIC5mID0gbW9kZWxfcHJlZCksCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIERUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzUsIC5mID0gbW9kZWxfcHJlZCkpICU+JSAKICAKICAgIGdhdGhlcihSZWdyZXNzaW9uLCBQcmVkaWN0aW9uLCAtZGF0YSwgLXdlZWspICU+JQogICAgbXV0YXRlKE9ic2VydmVkID0gbWFwKGRhdGEsIHB1bGwsIFRyaXBfQ291bnQpLAogICAgICAgICAgIEFic29sdXRlX0Vycm9yID0gbWFwMihPYnNlcnZlZCwgUHJlZGljdGlvbiwgIH4gYWJzKC54IC0gLnkpKSwKICAgICAgICAgICBNQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBtZWFuLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgIHNkX0FFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgc2QsIG5hLnJtID0gVFJVRSkpCndlZWtfcHJlZGljdGlvbnMKYGBgCgpBIDIgKHdlZWtzKSB0aW1lcyA1IChtb2RlbHMpIHRhYmxlIGlzIGNyZWF0ZWQgYWJvdmUsIGVhY2ggcm93IHByZXNlbnRzIHRoZSBzdGF0aXN0aWNzIG9mIG9uZSB3ZWVrIGluIGByaWRlLlRyZXN0LndlZWtOZXN0YCBwcmVkaWN0ZWQgYnkgYSByZWdyZXNzaW9uIG1vZGVsLgoKIyA1IEV4YW1pbmcgYWNjdXJhY3kKCkluIHRoaXMgc2VjdGlvbiwgTWVhbiBBYnNvbHV0ZSBFcnJvcihNQUUpIGlzIGNhbGN1bGF0ZWQgb24gYHJpZGUuVGVzdGAgZm9yIGVhY2ggbW9kZWwuCgojIyA1LjEgRXhhbWluZSBFcnJvciBNZXRyaWNzIGZvciBBY2N1cmFjeQoKRmlndXJlIDUtMSBzaG93cyBnb29kbmVzcyBvZiBmaXQgYnkgd2VlayBhbmQgbW9kZWwuIFRoZSBNQUUgZm9yIHRoZSB0aW1lIGVmZmVjdHMgbW9kZWwgKGByZWcxYCkgaW4gd2VlayAyMSBpcyBjb21wYXJhYmxlIHRvIHRoZSBtZWFuIG9ic2VydmVkIGBUcmlwX0NvdW50YCBvZiAwLjI1Ni4gV2l0aCBpbmNyZWFzaW5nIHNvcGhpc3RpY2F0aW9uLCB0aGUgbW9kZWwgYmVjb21lcyBtb3JlIGFjY3VyYXRlIGFuZCBtb3JlIGdlbmVyYWxpemFibGUuCgpgYGB7ciBwbG90X2Vycm9yc19ieV9tb2RlbCB9CndlZWtfcHJlZGljdGlvbnMgJT4lCiAgZHBseXI6OnNlbGVjdCh3ZWVrLCBSZWdyZXNzaW9uLCBNQUUpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgTUFFLCAtUmVncmVzc2lvbiwgLXdlZWspICU+JQogIGdncGxvdChhZXMod2VlaywgTUFFKSkgKyAKICAgIGdlb21fYmFyKGFlcyhmaWxsID0gUmVncmVzc2lvbiksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNSkgKwogICAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIEVycm9ycyBieSBtb2RlbCBzcGVjaWZpY2F0aW9uIGFuZCB3ZWVrIiwKICAgICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgNS0xIikgKwogIHBsb3RUaGVtZQpgYGAKCkZvciBlYWNoIG1vZGVsLCBwcmVkaWN0ZWQgYW5kIG9ic2VydmVkIGBUcmlwX0NvdW50YCBpcyB0YWtlbiBvdXQgb2YgdGhlIHNwYXRpYWwgY29udGV4dCBhbmQgdGhlaXIgbWVhbnMgb2YgcGxvdHRlZCBpbiB0aW1lIHNlcmllcyBmb3JtIGJlbG93LiBJdCBzZWVtcyB0aGF0IG1vZGVsIEEgYW5kIEMgaGF2ZSB0aGUgc2FtZSB0aW1lIHRyZW5kIGJlY2F1c2UgdGhlIHNwYXRpYWwgY29udGV4dCBoYXMgYmVlbiByZW1vdmVkLgoKVGhlIHRpbWUgbGFncyBmYWNpbGl0YXRlIHRoZSBjYXBhY2l0eSB0byBwcmVkaWN0IGZvciB0aGUgaGlnaGVzdCBwZWFrcyBhcyBzb3BoaXN0aWNhdGlvbiBpbmNyZWFzZXMuIAoKYGBge3IgZXJyb3JfdnNfYWN0dWFsX3RpbWVzZXJpZXMgLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBzdGFydF9zdGF0aW9uID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb24pKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQogICAgdW5uZXN0KCkgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtUmVncmVzc2lvbiwgLWludGVydmFsNjAsIC1zdGFydF9zdGF0aW9uKSAlPiUKICAgIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIFZhcmlhYmxlLCBpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShWYWx1ZSA9IHN1bShWYWx1ZSkpICU+JQogICAgZ2dwbG90KGFlcyhpbnRlcnZhbDYwLCBWYWx1ZSwgY29sb3VyPVZhcmlhYmxlKSkgKyAKICAgICAgZ2VvbV9saW5lKHNpemUgPSAxLjEpICsgCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlIHNoYXJlIHRpbWUgc2VyaWVzIiwgc3VidGl0bGUgPSAiTEE7IEEgdGVzdCBzZXQgb2YgMiB3ZWVrcyIsICB4ID0gIkhvdXIiLCB5PSAiU3RhdGlvbiBUcmlwcyIsCiAgICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDUtMiIpICsKICAgICAgcGxvdFRoZW1lCmBgYAoKRmlndXJlIDUtMyBpbGx1c3RyYXRlcyB0aGUgb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBkaXN0cmlidXRpb24gZm9yIGRpZmZlcmVudCB0aW1lcyBvZiBkYXkgZHVyaWcgd2Vla2RheXMgYW5kIHdlZWtlbmRzLiBJdCBpcyBjbGVhciB0aGF0IHRoZSBtb2RlbHMgYXJlIHVuZGVyIHByZWRpY3RpbmcgaW4gZ2VuZXJhbC4KCmBgYHtyIG9ic19wcmVkX2FsbCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZT1UUlVFfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIHN0YXJ0X2xhbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYW4pLCAKICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpKSAlPiUKICAgIHNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9sYW4sIHN0YXJ0X2xvbiwgCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbiwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sCiAgICAgICAgICAgZG90dykgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9wb2ludChhZXMoeD0gT2JzZXJ2ZWQsIHkgPSBQcmVkaWN0aW9uKSkrCiAgICBnZW9tX3Ntb290aChhZXMoeD0gT2JzZXJ2ZWQsIHk9IFByZWRpY3Rpb24pLCBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSsKICAgIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCkrCiAgZmFjZXRfZ3JpZCh0aW1lX29mX2RheX53ZWVrZW5kKSsKICBsYWJzKHRpdGxlPSJPYnNlcnZlZCB2cyBQcmVkaWN0ZWQiLAogICAgICAgeD0iT2JzZXJ2ZWQgdHJpcHMiLCAKICAgICAgIHk9IlByZWRpY3RlZCB0cmlwcyIsCiAgICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDUtMyIpKwogIHBsb3RUaGVtZQpgYGAKCiMjIDUuMiBTcGFjZS1UaW1lIEVycm9yIEV2YWx1YXRpb24KCkZpZ3VyZSA1LTQgcGxvdHMgbWVhbiBhYnNvbHV0ZSBlcnJvcnMgb24gbWFwIGJ5IHdlZWtkYXlzL3dlZWtlbmRzIGFuZCB0aW1lIG9mIGEgZGF5LgoKYGBge3Igc3RhdGlvbl9zdW1tYXJ5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIHN0YXJ0X2xhbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYW4pLCAKICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpICkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgc3RhcnRfbG9uLCAKICAgICAgICAgICBzdGFydF9sYW4sIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUKICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgc3RhcnRfbG9uLCBzdGFydF9sYW4pICU+JQogIHN1bW1hcml6ZShNQUUgPSBtZWFuKGFicyhPYnNlcnZlZC1QcmVkaWN0aW9uKSwgbmEucm0gPSBUUlVFKSklPiUKICBnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gTEFDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2xvbiwgeSA9IHN0YXJ0X2xhbiwgY29sb3IgPSBNQUUpLCAKICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMC41LCBhbHBoYSA9IDAuNCkrCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsKICB5bGltKG1pbihkYXRfY2Vuc3VzJHN0YXJ0X2xhbiksIG1heChkYXRfY2Vuc3VzJHN0YXJ0X2xhbikpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkc3RhcnRfbG9uKSwgbWF4KGRhdF9jZW5zdXMkc3RhcnRfbG9uKSkrCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycywgVGVzdCBTZXQiLAogICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA1LTQiKSsKICBtYXBUaGVtZQogIApgYGAKCiMjIDUuMyBTb2Npby1lY29ub21pYyB2YXJpYWJsZXMKCk9uZSBtaWdodCB0aGluayB0aGF0IHRoZSBzdGF0aW9uIGxvY2F0aW9ucyBwcm9iYWJseSByZWxhdGUgdG8gbGlrZWx5IHVzZXJzLCB3aG8gc2VlbSB0byBiZSBjb21tdXRpbmcgZG93bnRvd24gdG8gdGhlIGxvb3AuIEZpZ3VyZSA1LTQgcGxvdHMgTUFFIGJ5IHRocmVlIHNvY2lvLWVjb25vbWljIHZhcmlhYmxlczogbWVkaWFuIGluY29tZSwgcGVyY2VudCB0YWtpbmcgcHVibGljIHRyYW5zcG9ydGF0aW9uIGFuZCBwZXJjZW50IG9mIHdoaXRlLiBTZXZlcmFsIHN0YXRpb25zIHNob3cgcmVzaXN0YW5jZSB0byB0aGUgbW9kZWxzIC0gdGhleSBoYXZlIGhpZ2ggaW5jb21lLCBsb3cgcHVibGljIHRyYW5zcG9ydGF0aW9uIHVzYWdlIGFuZCBtb3JlIHRoYW4gYSBoYWxmIG9mIHdoaXRlIHBvcHVsYXRpb24uCgpgYGB7ciBzdGF0aW9uX3N1bW1hcnkyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIHN0YXJ0X2xhbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYW4pLCAKICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpLAogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLAogICAgICAgICAgIE1lZF9JbmMgPSBtYXAoZGF0YSwgcHVsbCwgTWVkX0luYyksCiAgICAgICAgICAgUGVyY2VudF9XaGl0ZSA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1doaXRlKSkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgc3RhcnRfbG9uLCAKICAgICAgICAgICBzdGFydF9sYW4sIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcsIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucywgTWVkX0luYywgUGVyY2VudF9XaGl0ZSkgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQogIGZpbHRlcih0aW1lX29mX2RheSA9PSAiQU0gUnVzaCIpICU+JQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucywgTWVkX0luYywgUGVyY2VudF9XaGl0ZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQogIGdhdGhlcigtc3RhcnRfc3RhdGlvbiwgLU1BRSwga2V5ID0gInZhcmlhYmxlIiwgdmFsdWUgPSAidmFsdWUiKSU+JQogIGdncGxvdCguKSsKICAjZ2VvbV9zZihkYXRhID0gTEFDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHZhbHVlLCB5ID0gTUFFKSwgYWxwaGEgPSAwLjQpKwogIGdlb21fc21vb3RoKGFlcyh4ID0gdmFsdWUsIHkgPSBNQUUpLCBtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UpKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpKwogIGxhYnModGl0bGU9IkVycm9ycyBhcyBhIGZ1bmN0aW9uIG9mIHNvY2lvLWVjb25vbWljIHZhcmlhYmxlcyIsCiAgICAgICB5PSJNZWFuIEFic29sdXRlIEVycm9yIChUcmlwcykiLAogICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA1LTQiKSsKICBwbG90VGhlbWUKICAKYGBgCgojIDYgQ3Jvc3MtVmFsaWRhdGlvbgoKQmVsb3csIGEgcGFyYW1ldGVyIGNhbGxlZCBgZml0Q29udHJvbGAgaXMgc2V0IHRvIHNwZWNpZnkgdGhlIG51bWJlciBvZiBrLWZvbGQgcHJ0aXRpb25zIC0gaW4gdGhpcyBjYXNlIDEwMC4gSW4gdGhlIGNvZGUgYmVsb3csIGBzZXQuc2VlZGAgZW5zdXJlcyByZXByb2R1Y2libGUgZm9sZHMuIEFuIG9iamVjdCBgcmVnLmN2YCwgaXMgZXN0aW1hdGVkIHVzaW5nIHRoZSBzYW1lIHJlZ3Jlc3Npb24gYXMgc3BlY2lmaWVkIGluIGByZWcgNGAuCgpgYGB7ciBjdn0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTAwKQpzZXQuc2VlZCg4ODg4KQoKcmVnLmN2IDwtIAogIHRyYWluKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSwgCiAgICAgZGF0YT1yaWRlLnBhbmVsLCAKICAgICBtZXRob2QgPSAibG0iLCB0ckNvbnRyb2wgPSBmaXRDb250cm9sLCBuYS5hY3Rpb24gPSBuYS5wYXNzKQoKcmVnLmN2CmBgYAoKVGhlIGNyb3NzLXZhbGlkYXRpb24gb3V0cHV0IHByb3ZpZGVzIHZlcnkgaW1wb3J0YW50IGdvb2RuZXNzIG9mIGZpdCBpbmZvcm1hdGlvbi4gVGhlIGByZXNhbXBsZWAgc2hvd3MgdGhlIGZpcnN0IGZpdmUgb3V0cHV0cyBvZiAxMDAgZm9sZHMuCgpgYGB7cn0KcmVnLmN2JHJlc2FtcGxlWzE6NSxdCmBgYAoKYGBge3IgcGxvdF9jdiwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpNQUUgPC0gcmVnLmN2JHJlc2FtcGxlWywzXQpkZiA8LSBkYXRhLmZyYW1lKE1BRSkKCmdncGxvdChkZiwgYWVzKHg9TUFFKSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5PS4uZGVuc2l0eS4uKSxiaW5zPTM1LCBmaWxsPSIjRkI4QzZGIikgKwogIGxhYnMoCiAgICB4PSJDcm9zcy1WYWxpZGF0aW9uIE1BRSBIaXN0b2dyYW0iLAogICAgeT0iTUFFIiwKICAgIGNhcHRpb24gPSAiRmlndXJlIDYtMSIKICApCnBsb3RUaGVtZQpgYGAKCiMgNyBDb25jbHVzaW9uCgpJIGJlZ2FuIGJ5IGV4cGxvcmluZyBzZXJpYWwgYXV0b2NvcnJlbGF0aW9uIGFuZCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBvZiB0aGUgTEEgTWV0cm8gQmlrZSBTaGFyZSB0cmlwIGRhdGEuIEJpa2Ugc2hhcmUgY291bnRzIERhdGEgYnkgc3RhdGlvbiBzaG93cyBjZXJ0YWluIHRpbWUgcGF0dGVybnMgZHVyaW5nIG9uZSB3ZWVrIG9yIG9uZSBkYXk6IGJpa2VzIGFyZSBoaWdobHkgdXNlZCBvbiB3ZWVrZW5kcyBhbmQgbGVzcyBvbiBXZWRuZXNkYXlzIGFuZCBUaHVyc2RheXMsIDEyOjAwLTE3OjAwIGV2ZXJ5IGRheSBpcyB0aGUgcGVhayB1c2FnZSBwZXJpb2QuIEJpa2Ugc2hhcmUgY291bnRzIGFsc28gc2hvd3Mgc3BhdGlhbCBhZ2dsb21lcmF0aW9uLCB0aGF0IGlzIGNlcnRhaW4gc3RhdGlvbnMgdGVuZCB0byBoYXZlIGhpZ2hlciBiaWtlIHVzYWdlcy4KClRoZW4sIGZpdmUgcmVncmVzc2lvbiBtb2RlbHMgYXJlIGJ1aWx0LiBEaXZpZGUgdGhlIGZpcnN0IHRocmVlIHdlZWtzIGFzIHRoZSB0cmFpbmluZyBzZXQsIGFuZCB0aGUgbmV4dCB0d28gd2Vla3MgYXMgdGhlIHRlc3Qgc2V0LiBCeSBjYWxjdWxhdGluZyB0aGUgcHJlZGljdGlvbiBhbmQgZXJyb3IgZGF0YSBvZiB0aGUgbW9kZWwgaW4gdGhlIHRlc3Qgc2V0LCB0aGUgYWNjdXJhY3kgY2FuIGJlIGV4YW1pbmVkIHRoYXQgdGhlIGVycm9ycyBhcmUgcmVsYXRpdmVseSBzbWFsbC4gCgo=